package com.chen.authority.common.aspect;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapWrapper;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.Header;
import cn.hutool.http.HttpStatus;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONUtil;
import com.chen.authority.common.constant.SysConstant;
import com.chen.authority.common.annotation.Log;
import com.chen.authority.common.response.Response;
import com.chen.authority.common.util.AddressUtil;
import com.chen.authority.common.util.RequestUtil;
import com.chen.authority.common.util.SaTokenUtil;
import com.chen.authority.mapper.LoginLogMapper;
import com.chen.authority.mapper.OperationLogMapper;
import com.chen.authority.model.dto.LoginDto;
import com.chen.authority.model.po.LoginLogPo;
import com.chen.authority.model.po.OperationLogPo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * 统一日志切面
 *
 * @author chengy
 * @date 2022/09/04
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LogAspect {

    /* 计算操作耗时 */
    private static final ThreadLocal<Long> TIME_THREAD_LOCAL = new NamedThreadLocal<>("Cost Time");

    private final LoginLogMapper loginLogMapper;
    private final OperationLogMapper operationLogMapper;

    /**
     * 记录登录日志
     */
    @AfterReturning(returning = "result", pointcut = "execution(public * com.chen.authority.controller.LoginController.login(..))")
    public void loginLog(JoinPoint joinPoint, Response result) {
        HttpServletRequest request = RequestUtil.getRequest();

        LoginLogPo loginLog = new LoginLogPo();
        loginLog.setLoginTime(new Date());
        loginLog.setMessage(result.getCode() == HttpStatus.HTTP_OK ? "登录成功" : result.getMessage());
        loginLog.setUsername(((LoginDto) joinPoint.getArgs()[0]).getUsername());

        String ip = ServletUtil.getClientIP(request);
        loginLog.setIp(ip);
        loginLog.setLocation(AddressUtil.getRealAddressByIP(ip));

        UserAgent userAgent = UserAgentUtil.parse(request.getHeader(Header.USER_AGENT.toString()));
        loginLog.setBrowser(userAgent.getBrowser().toString() + StrUtil.SPACE + userAgent.getVersion());
        loginLog.setOs(userAgent.getOs().toString());

        loginLog.setStatus(result.getCode() == HttpStatus.HTTP_OK ? SysConstant.SUCCESS : SysConstant.FAIL);

        loginLogMapper.insert(loginLog);
    }

    /**
     * 记录操作日志
     */
    @Around("@annotation(controllerLog)")
    public Object operationLog(ProceedingJoinPoint joinPoint, Log controllerLog) throws Throwable {
        TIME_THREAD_LOCAL.set(System.currentTimeMillis());
        Response result = (Response) joinPoint.proceed();
        handleLog(joinPoint, controllerLog, null, result);
        return result;
    }

    /**
     * 拦截异常操作
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "exception")
    public void saveExceptionOperationLog(JoinPoint joinPoint, Log controllerLog, Exception exception) {
        handleLog(joinPoint, controllerLog, exception, null);
    }

    /**
     * 记录日志
     */
    private void handleLog(JoinPoint joinPoint, Log controllerLog, Exception exception, Response result) {
        try {
            HttpServletRequest request = RequestUtil.getRequest();

            OperationLogPo operationLog = new OperationLogPo();

            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            operationLog.setModule(controllerLog.module());
            operationLog.setBusinessType(controllerLog.businessType().name());

            String classPath = joinPoint.getTarget().getClass().getName();
            String methodName = signature.getMethod().getName();
            String methodPath = classPath + StrPool.DOT + methodName;

            operationLog.setMethod(methodPath);
            operationLog.setReqMethod(request.getMethod());

            Map<String, String> rtnMap = convertMap(request.getParameterMap());
            operationLog.setReqParam(CollUtil.isEmpty(rtnMap) ?
                    getParameter(method, joinPoint.getArgs()) : JSONUtil.toJsonStr(rtnMap));

            if (ObjectUtil.isNotNull(result)) {
                operationLog.setRespParam(JSONUtil.toJsonStr(result));
                operationLog.setStatus(result.getCode() == HttpStatus.HTTP_OK ?
                        SysConstant.SUCCESS : SysConstant.FAIL);
            } else {
                operationLog.setStatus(ObjectUtil.isNull(exception) ?
                        SysConstant.SUCCESS : SysConstant.FAIL);
            }

            operationLog.setOperator(SaTokenUtil.getLoginUsername());
            String ip = ServletUtil.getClientIP(request);
            operationLog.setIp(ip);
            operationLog.setLocation(AddressUtil.getRealAddressByIP(ip));
            operationLog.setReqUrl(request.getRequestURI());
            operationLog.setOperTime(new Date());
            operationLog.setCostTime(System.currentTimeMillis() - TIME_THREAD_LOCAL.get());

            operationLogMapper.insert(operationLog);
        } catch (Exception e) {
            log.error("==操作异常==");
            log.error("异常信息:{}", e.getMessage());
            e.printStackTrace();
        } finally {
            TIME_THREAD_LOCAL.remove();
        }

    }

    /**
     * 转换request 请求参数
     */
    private Map<String, String> convertMap(Map<String, String[]> paramMap) {
        Map<String, String> rtnMap = new HashMap<>();
        for (String key : paramMap.keySet()) {
            rtnMap.put(key, paramMap.get(key)[0]);
        }
        return new MapWrapper<>(rtnMap);
    }

    /**
     * 获取请求参数（JSON格式）
     */
    private String getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        // 从方法参数列表中筛选请求参数
        for (int i = 0; i < parameters.length; i++) {
            // 将RequestBody注解修饰的参数作为请求参数
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                argList.add(args[i]);
            }
            // 将PathVariable注解修饰的参数作为请求参数
            PathVariable pathVariable = parameters[i].getAnnotation(PathVariable.class);
            if (pathVariable != null) {
                Map<String, Object> map = new HashMap<>();
                String key = StringUtils.isEmpty(pathVariable.value()) ?
                        parameters[i].getName() : pathVariable.value();
                map.put(key, args[i]);
                argList.add(map);
            }
        }
        return CollUtil.isEmpty(argList) ? null : JSONUtil.parse(argList).toString();
    }

}